package com.craftworks.tiles;

import java.awt.*;
import java.util.Arrays;

/**
 * 提供紧凑排列 Tile 的布局器。
 */
class TileLayout implements LayoutManager
{
    private static final int GAP = 5; // 砖块之间以及外边框间距
    private static final int TILE_WIDTH = AbstractTile.Size.LANDSCAPE.width;
    private static final int TILE_HEIGHT = AbstractTile.Size.LANDSCAPE.height;

    private boolean[][] grids;

    static class LayoutContext
    {
        int limit;
        int x, y, row, col;
        Insets insets;
    }

    @Override
    public void addLayoutComponent(String name, Component comp)
    {
        if (!(comp instanceof AbstractTile))
            throw new IllegalArgumentException("component is not a tile");
    }

    @Override
    public void removeLayoutComponent(Component comp)
    {
    }

    @Override
    public Dimension preferredLayoutSize(Container parent)
    {
        synchronized (parent.getTreeLock())
        {
            Insets insets = parent.getInsets();
            Dimension dim = new Dimension(0, 0);
            dim.width += insets.left + insets.right + GAP + (TILE_WIDTH + GAP) * 5;
            dim.height += insets.top + insets.bottom + GAP + (TILE_HEIGHT + GAP) * 5;
            return dim;
        }
    }

    @Override
    public Dimension minimumLayoutSize(Container parent)
    {
        synchronized (parent.getTreeLock())
        {
            Insets insets = parent.getInsets();
            Dimension dim = new Dimension(0, 0);
            dim.width += insets.left + insets.right + GAP + TILE_WIDTH + GAP;
            dim.height += insets.top + insets.bottom + GAP + TILE_HEIGHT + GAP;
            return dim;
        }
    }

    @Override
    public void layoutContainer(Container parent)
    {
        // 布局策略：
        // 从左至右、从上至下依次排列，不超过当前容器的宽度（但每行至少有一个Tile，除非有大号Tile挤占了空间）；
        // 换行后仍然从最左侧开始布局，除非当前空间（宽度）不足以容纳当前Tile；跳过挤占空间的Tile，继续在当前行寻找可用空间。
        // 由于Tile最大占据两行，因此最多挤占一行的空间。
        // 布局器不改变当前Tile的大小，只移动它们的位置。
        // 行高永远是固定的（一个小Tile的高度）
        synchronized (parent.getTreeLock())
        {
            Insets insets = parent.getInsets();
            int xLimit = parent.getWidth() - insets.right;

            if (xLimit < insets.left + GAP + TILE_WIDTH)
                return; // 当前宽度不够布局空间，不浪费时间进行布局了（为了避免后面的while循环无法退出）。

            int nComponents = parent.getComponentCount();
            LayoutContext context = new LayoutContext();
            context.x = insets.left + GAP;
            context.y = insets.top + GAP;
            context.row = 0;
            context.col = 0;
            context.limit = xLimit;
            context.insets = insets;

            initGrids(nComponents);

            for (int i = 0; i < nComponents; i++)
            {
                Component component = parent.getComponent(i);
                if (component.isVisible())
                {
                    Dimension size = component.getPreferredSize();
                    component.setSize(size);

                    layoutComponent(component, size, context);
                }
            }
        }
    }

    public int addPlaceholderAt(Container parent, Dimension placeholder, Point location)
    {
        synchronized (parent.getTreeLock())
        {
            Insets insets = parent.getInsets();
            int xLimit = parent.getWidth() - insets.right;

            if (xLimit < insets.left + GAP + TILE_WIDTH)
                return -1;

            int nComponents = parent.getComponentCount();
            LayoutContext context = new LayoutContext();
            context.x = insets.left + GAP;
            context.y = insets.top + GAP;
            context.row = 0;
            context.col = 0;
            context.limit = xLimit;
            context.insets = insets;

            initGrids(nComponents + 1);

            boolean settled = false;
            int compIndex = -1;
            for (int i = 0; i < nComponents; i++)
            {
                Component component = parent.getComponent(i);
                if (component.isVisible())
                {
                    if (!settled && isAheadOfComponent(location, placeholder, component))
                    {
                        settled = true;
                        compIndex = i;

                        layoutComponent(null, placeholder, context);
                    }

                    layoutComponent(component, component.getPreferredSize(), context);
                }
            }

            return compIndex;
        }
    }

    private boolean isAheadOfComponent(Point p, Dimension d, Component c)
    {
        // 判定在p点摆放大小为d的Tile应该位于c的前面的情况：
        // 1、p位于c中心点的左上方（无论t的大小）
        // 2、p位于c中心点的左下方，且没有超出底边，且t的高度大于等于c
        // 3、p位于c顶边的上方（右上方）

        return (p.x < c.getX() + c.getWidth() / 2 && p.y <= c.getY() + c.getHeight() / 2) ||
               (p.x < c.getX() + c.getWidth() / 2 && p.y <= c.getY() + c.getHeight() && d.height >= c.getHeight()) ||
               (p.y < c.getY());
    }

    private void initGrids(int nComponents)
    {
        // 这里是对网格占用标记做的优化，避免重复创建细碎的标记对象。
        // 因为一个Tile最大占用2x2个网格，所以这里宽/高都扩大2倍。
        int maxCols = nComponents * 2;
        int maxRows = nComponents * 2;

        if (grids == null || grids.length < maxRows || grids[0].length < maxCols)
            grids = new boolean[maxRows][maxCols];

        for (boolean[] row : grids)
            Arrays.fill(row, false);
    }

    private boolean tryOccupyGrids(int row, int col, int width, int height)
    {
        // 一个Tile最多占用2x2个网格
        int rows = (height > TILE_HEIGHT) ? 2 : 1;
        int cols = (width > TILE_WIDTH) ? 2 : 1;
        for (int r = 0; r < rows; r++)
            for (int c = 0; c < cols; c++)
                if (grids[r + row][c + col])
                    return false;

        for (int r = 0; r < rows; r++)
            for (int c = 0; c < cols; c++)
                grids[r + row][c + col] = true;
        return true;
    }

    private void layoutComponent(Component c, Dimension d, LayoutContext context)
    {
        // 布局原则：
        // 1、布局空间被平均划分为 2n * 2n 个网格（n为当前Tile数量），一个Tile可以占居1x1、1x2、2x1、2x2个网格。
        // 2、可见Tile依次从左向右、从上向下逐个搜索可用的网格空间，进行占位布局。
        // 3、以容器宽度控制布局行宽度，当行尾空间不足（Tile宽度越过了右边界）时，换至下一行进行布局。
        // 4、不允许出现空行；当行首元素宽度超过了右边界时，需要强制布局（覆盖原则3）。

        // location: 当前布局点在容器中的位置
        // grid: 当前布局点对应的网格位置（x、y分别对应网格的列索引和行索引）
        while (true)
        {
            // 因为 tryOccupyGrids 一定能找到空白区域，所以这里能够安全退出 while 循环。
            if ((context.x == context.insets.left + GAP || context.x + d.width < context.limit) &&
                tryOccupyGrids(context.row, context.col, d.width, d.height))
            {
                if (c != null)
                    c.setLocation(context.x, context.y);
                context.x += d.width + GAP;
                context.col += (d.width > TILE_WIDTH) ? 2 : 1;
                return;
            }

            // 没有合适的位置，向后移动一个单元格
            context.x += TILE_WIDTH + GAP;
            context.col++;
            if (context.x + d.width > context.limit)
            {
                // 行尾空间不足，换到下一行
                context.x = context.insets.left + GAP;
                context.y += TILE_HEIGHT + GAP;
                context.col = 0;
                context.row++;
            }
        }
    }
}
